package com.chenfwind.utils;

/** 
 * 文件名：  APIUtils.java
 * @author  陈锋
 * @最后修改人： 陈锋 
 * @最后修改日期：	2015-9-21 下午3:13:25
 * @version:5.0
 * Copyright 珠海市恒源信息技术有限公司 Corporation 2015
 */ 
    


import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.Map.Entry;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ConcurrentHashMap;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.chenfwind.common.Constant;
import com.chenfwind.common.ResultVO;
import com.chenfwind.entity.VisitLog;


/** 
 * 类描述： 
 * @author  陈锋
 * @最后修改人： 陈锋 
 * @最后修改日期：	2015-9-21 下午3:13:25
 * @version  0.5
 */

public class APIUtils {
	
	public static final List<String> listForNoLoginAPI = new ArrayList<String>();//不要求先登录的接口列表

	public static final List<String> listForNoChecksignAPI = new ArrayList<String>();//不要求验签的接口列表

	public static final int MAX_ACCESS_TIME = 1 * 60 * 1000;// 访问限制时间

	public final static ConcurrentHashMap<String, VisitLog> visitLogMap = new ConcurrentHashMap<String, VisitLog>();//存储Uid对应的访问信息 
	
	static{
		listForNoLoginAPI.add("login");
		listForNoLoginAPI.add("register");
		listForNoLoginAPI.add("resetUserPassword");
		listForNoLoginAPI.add("modifyUserInfo");
		listForNoLoginAPI.add("sendSmsAuthCode");
		listForNoLoginAPI.add("checkSmsAuthCode");
		listForNoLoginAPI.add("getHomeEpisodeList");
		listForNoLoginAPI.add("getEpisodeInfo");
		listForNoLoginAPI.add("getSimpleRegionList");
		listForNoLoginAPI.add("getRegionSchoolList");
		listForNoLoginAPI.add("getGradeList");
		listForNoLoginAPI.add("getClassList");
		listForNoLoginAPI.add("getVocabularyInfo");
		listForNoLoginAPI.add("getLatestTerminalSoftware");
		listForNoLoginAPI.add("getLatestTerminalSoftwareByWeb");
		
		listForNoChecksignAPI.add("getLatestTerminalSoftwareByWeb");
		listForNoChecksignAPI.add("checkParamsFail");
		listForNoChecksignAPI.add("download");
	}
	
	/** 
	 *  genResponseResult 方法描述：生成带响应签名的返回结果
	 * @author  陈锋
	 * @最后修改人： 陈锋 
	 * @最后修改日期：	2015-9-21 下午4:12:13
	 * @version  0.5 
	 *
	 * @param result
	 * @return
	 * @return ResultVO
	 */
	public static ResultVO<Object> genResponseResult(ResultVO<Object> resultVO)throws Exception{
//		Object result = resultVO.getResult();
//		Object status = resultVO.getStatus();
//		String plaintext = "";
//		plaintext = "status="+status+"&result="+JSONObject.toJSONString(result);
//		if(StringUtils.isNull(plaintext)){
//			plaintext = "status="+status;
//		}
		Object result = resultVO.getResult();
		if(result != null){
			resultVO.setResult(JSONObject.toJSONString(result));
		}
		resultVO.setMessage(null);
		resultVO.setResultCount(null);
		Map<String, Object> map = (Map<String, Object>) JSON.parse(JSONObject.toJSONString(resultVO));
		TreeMap<String,Object> params = APIUtils.map2TreeMap(map);
		StringBuffer stringBuffer = new StringBuffer();
		for (Entry<String, Object> entry : params.entrySet()) {
			stringBuffer.append("&"+entry.toString());
		}
		String plaintext = stringBuffer.toString().substring(1);
		String signature = genSHA1(Constant.SECRET_KEY, plaintext);
		System.out.println(signature);
		resultVO.setSig(signature);
		return resultVO;
	}
	
	/** 
	 *  genSHA1 方法描述：生成SHA1码
	 * @author  陈锋
	 * @最后修改人： 陈锋 
	 * @最后修改日期：	2015-9-21 下午4:15:47
	 * @version  0.5 
	 *
	 * @param secretkey
	 * @param plaintext
	 * @return
	 * @return String
	 */
	public static String genSHA1(String secretkey, String plaintext){
		try {
			return EncryptUtils.encryptHMAC(secretkey, plaintext);
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}
	
	/** 
	 *  setTokenForVisitLog 方法描述：对应uid生成token存储于visitLogMap，并返回token
	 * @author  陈锋
	 * @最后修改人： 陈锋 
	 * @最后修改日期：	2015-9-21 下午4:24:03
	 * @version  0.5 
	 *
	 * @param uid
	 * @return
	 * @return String
	 */
	public static String setTokenForVisitLog(String uid){
		String tokenString = StringUtils.genUUID();
		VisitLog visitLog = visitLogMap.get(uid);
		Set<String> token = null;
		if(visitLog == null || StringUtils.isNull(visitLog.getUid())){
			visitLog = new VisitLog();
			token = new HashSet<String>();
		}else{
			token = visitLog.getToken();
			if(token == null){
				token = new HashSet<String>();
			}
		}
		token.add(tokenString);
		visitLog.setToken(token);
		visitLog.setUid(uid);
		visitLog.setTokenStartedDate(new Date());
		visitLogMap.put(uid, visitLog);
		return tokenString;
	}
	
	/** 
	 *  getTokenForVisitLog 方法描述：获取visitLogMap对应uid的token值
	 * @author  陈锋
	 * @最后修改人： 陈锋 
	 * @最后修改日期：	2015-9-21 下午4:35:08
	 * @version  0.5 
	 *
	 * @param uid
	 * @return
	 * @return String
	 */
	public static Set<String> getTokenForVisitLog(String uid){
		return visitLogMap.get(uid).getToken();
	}
	
	/** 
	 *  clearCntForVisitLog 方法描述：清空visitLog的visitArray
	 * @author  陈锋
	 * @最后修改人： 陈锋 
	 * @最后修改日期：	2015-9-21 下午4:37:34
	 * @version  0.5 
	 *
	 * @return void
	 */
	public static void clearCntForVisitLog(){
		ArrayBlockingQueue<Long> visitArray;
		long timeNow = new Date().getTime();
		if(visitLogMap != null && visitLogMap.size() > 0){
			for (Map.Entry<String, VisitLog> entry : visitLogMap.entrySet()) {
				visitArray = entry.getValue().getVisitArray();
				//清除所有大于一分钟的元素
				while(visitArray != null && !visitArray.isEmpty() && timeNow - visitArray.element() > MAX_ACCESS_TIME) {
					visitArray.poll();
				}
			}
		}
	}
	
	/** 
	 *  addVisitCountForVisitLog 方法描述： 访问次数加一
	 * @author  陈锋
	 * @最后修改人： 陈锋 
	 * @最后修改日期：	2015-9-21 下午4:38:49
	 * @version  0.5 
	 *
	 * @return
	 * @return String
	 */
	public static synchronized Integer addVisitCountForVisitLog(String uid){
		VisitLog visitLog = visitLogMap.get(uid);
		ArrayBlockingQueue<Long> visitArray = null;
		long timeNow = new Date().getTime();//获取当前时间
		if(visitLog == null || StringUtils.isNull(visitLog.getUid())){
			visitLog =  new VisitLog();
			visitLog.setUid(uid);
			visitArray = new ArrayBlockingQueue<Long>(120);
		}else{
			visitArray = visitLog.getVisitArray();
			
			if(visitArray == null){
				visitArray = new ArrayBlockingQueue<Long>(Constant.VISIT_MAX_FREQUENCY);
			}
			//清除所有大于一分钟的元素
			while(!visitArray.isEmpty() && timeNow - visitArray.element() > MAX_ACCESS_TIME) {
				visitArray.poll();
			}
		}
		//访问次数加1
		visitArray.add(timeNow);
		visitLog.setVisitArray(visitArray);
		visitLogMap.put(uid, visitLog);
		
		return visitArray.size();
	}
	
	/** 
	 *  checkParams 方法描述：验证公共参数，当验证不通过时返回相应status
	 * @author  陈锋
	 * @最后修改人： 陈锋 
	 * @最后修改日期：	2015-9-21 下午4:39:52
	 * @version  0.5 
	 *
	 * @return
	 * @return String
	 */
	public static String checkParams(boolean ifForNoLoginAPI,HttpSession session,String uid, String method, String secretid, String nonce, Long timestamp, String sig, String token, String url,TreeMap<String, Object> params){
		String result = null;
		if(!Constant.CODE_SUCCESS.toString().equals(result = checkUid(uid, token,ifForNoLoginAPI))){
			return result;
		}
		if(!ifForNoLoginAPI){
			addVisitCountForVisitLog(uid);
//			Set<String> usertoken = getTokenForVisitLog(uid);
		}
		if(!Constant.CODE_SUCCESS.toString().equals(result = checkTimestamp(timestamp))){
			return result;
		}else if(!Constant.CODE_SUCCESS.toString().equals(result = checkNonce(session, method, nonce))){
			return result;
		}else if(!Constant.CODE_SUCCESS.toString().equals(result = checkSig(sig, url, params))){
			return result;
		}
		
		return Constant.CODE_SUCCESS.toString();
	}
	
	/** 
	 *  checkUid 方法描述：检验uid、token的有效性,验证uid是否访问频率超限及验证uid是否锁定中，当验证不通过时返回相应status
	 * @author  陈锋
	 * @最后修改人： 陈锋 
	 * @最后修改日期：	2015-9-21 下午4:43:08
	 * @version  0.5 
	 *
	 * @return
	 * @return String
	 */
	public static String checkUid(String userId, String token, boolean ifForNoLoginAPI){
//		if(uid == null){
//			return HYConstant.CODE_NOLOGIN.toString();
//		}
		EHCacheUtils ehCacheUtils = EHCacheUtils.getInstance();//获取缓存对象
		if (ehCacheUtils.getCache(userId) != null) {//获取缓存判断uid是否存在，存在则被锁定
			return Constant.CODE_FREQUENCY_OVERRUN_APPID_URI_UID.toString();//访问频率超限，并未解锁
		}
		VisitLog visitLog = visitLogMap.get(userId);
		if(visitLog == null || StringUtils.isNull(visitLog.getUid())){
			if(!ifForNoLoginAPI){
				return Constant.CODE_NOLOGIN.toString();
			}
		}else{
			ArrayBlockingQueue<Long> visitArray = visitLog.getVisitArray();
			long timeNow = new Date().getTime();//获取当前时间
			//一分钟内访问120次则锁定用户
			if (visitArray !=null && visitArray.size() == Constant.VISIT_MAX_FREQUENCY&& timeNow - visitArray.poll() < MAX_ACCESS_TIME) {
				ehCacheUtils.putCache(userId, String.valueOf(timeNow), Constant.LOCK_TIME);//访问频率超限，锁定10分钟
				return Constant.CODE_FREQUENCY_OVERRUN_APPID_URI_UID.toString();//访问频率超限
			}
			
			if(!ifForNoLoginAPI && !visitLog.getToken().contains(token)){
				return Constant.CODE_TOKEN_INVALID.toString();//返回登录超时
			}
		}
		
		return Constant.CODE_SUCCESS.toString();
	}
	
	/** 
	 *  checkTimestamp 方法描述：检验timestamp(发起请求的时间)比调用接口的时间是否早超过2小时，当验证不通过时返回相应status
	 * @author  陈锋
	 * @最后修改人： 陈锋 
	 * @最后修改日期：	2015-9-21 下午4:48:14
	 * @version  0.5 
	 *
	 * @return
	 * @return String
	 */
	public static String checkTimestamp(Long timestamp){
		long timeNow = new Date().getTime();
		if (timeNow - timestamp > 2 * 60 * 60 * 1000) {
			return Constant.CODE_EXPIRED_TIMESTAMP.toString();
		}else{
			return Constant.CODE_SUCCESS.toString();
		}
	}
	
	/** 
	 *  checkNonce 方法描述：检验nonce(随机数)与最近一次对同一个接口的访问是否有重复，当验证不通过时返回相应status
	 * @author  陈锋
	 * @最后修改人： 陈锋 
	 * @最后修改日期：	2015-9-21 下午4:49:10
	 * @version  0.5 
	 *
	 * @return
	 * @return String
	 */
	public static String checkNonce(HttpSession session,String method,String nonce){
		ServletContext application = session.getServletContext();
		String oldNonce = (String)application.getAttribute(method);
		if(nonce.length() != 64){
			return Constant.CODE_REPLAY_ATTACKS.toString();
		}else{
			if(oldNonce != null && oldNonce.equals(nonce)){
				return Constant.CODE_REPLAY_ATTACKS.toString();
			}else{
				application.setAttribute(method, nonce);
				return Constant.CODE_SUCCESS.toString();
			}
		}
	}
	
	/** 
	 *  checkSig 方法描述：验签，当验证不通过时返回相应status
	 * @author  陈锋
	 * @最后修改人： 陈锋 
	 * @最后修改日期：	2015-9-21 下午4:50:12
	 * @version  0.5 
	 *
	 * @return
	 * @return String
	 */
	public static String checkSig(String sig,String url,TreeMap<String, Object> params){
		try {
			String plaintext = getPlaintext(url, params);
			String signature =  EncryptUtils.encryptHMAC(Constant.SECRET_KEY, plaintext);//获取hash签名
			System.out.println("signature:"+signature);
			if(!signature.equals(sig)){
				return Constant.CODE_SIGN_FAILED.toString();//返回验签失败
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return Constant.CODE_SUCCESS.toString();
	}
	
	/** 
	 *  getPlaintext 方法描述：组成plaintext字符串
	 * @author  陈锋
	 * @最后修改人： 陈锋 
	 * @最后修改日期：	2015-10-8 上午10:12:56
	 * @version  0.5 
	 *
	 * @return
	 * @return String
	 */
	private static String getPlaintext(String url, TreeMap<String, Object> params){
		StringBuffer stringBuffer = new StringBuffer();
		stringBuffer.append("body=&method=");
		if(url.contains("?")){
			stringBuffer.append("GET&url="+url);
		}else{
			stringBuffer.append("POST&url="+url);
		}
		for (Entry<String, Object> entry : params.entrySet()) {
			stringBuffer.append("&"+entry.toString());
		}
		return stringBuffer.toString();
	}
	
	/** 
	 *  map2TreeMap 方法描述：按字典升序排序
	 * @author  陈锋
	 * @最后修改人： 陈锋 
	 * @最后修改日期：	2015-11-9 下午2:48:08
	 * @version  0.5 
	 *
	 * @param map
	 * @return
	 * @throws Exception
	 * @return TreeMap<String,Object>
	 */
	public static TreeMap<String,Object> map2TreeMap(Map<String,Object> map)throws Exception{
		if(map != null){
			TreeMap<String,Object> treeMap = new TreeMap<String,Object>();
			for(Map.Entry<String, Object> entry : map.entrySet()){
				Object value = entry.getValue();				
				treeMap.put(entry.getKey(), value);
			}
			return treeMap;
		}else{
			return null;
		}
	}
	
}
